#!/usr/bin/env python3
"""
Job MD5 Utilities for duplicate job detection

This module provides functions to calculate all_params_md5 for jobs,
following the same logic as compass-ci's Crystal implementation.
"""

import hashlib
import json
from typing import Dict, Any, List

# Common parameters that are included in all_params_md5 calculation
COMMON_PARAMS = ['tbox_group', 'os', 'os_arch', 'os_version']


def flat_hh(hh: Dict[str, Any]) -> Dict[str, str]:
    """
    Flatten nested hash structure to key-value pairs

    Args:
        hh: Nested hash structure (e.g., pp or ss parameters)

    Returns:
        Flattened dictionary with keys like "program_name.param_name"
    """
    temp_hash = {}

    for k, v in hh.items():
        if v is None:
            continue

        if isinstance(v, dict):
            for kk, vv in v.items():
                if vv is None:
                    continue
                # Skip if key starts with '_' (matching Crystal's behavior)
                # These are typically metadata fields like _url that shouldn't affect MD5
                #if kk.startswith('_'):
                #    continue
                temp_hash[f"{k}.{kk}"] = str(vv)

    return temp_hash


def get_md5(data: Dict[str, str]) -> str:
    """
    Calculate MD5 hash of sorted key-value pairs

    This mimics Crystal's behavior: data.to_a.sort.to_s
    Crystal converts hash to array of tuples, sorts, and converts to string.
    Tuples in Crystal use curly braces {}, not square brackets [].

    Args:
        data: Dictionary of key-value pairs

    Returns:
        MD5 hash as hexadecimal string
    """
    # Sort by keys
    sorted_items = sorted(data.items())

    # Crystal format: Array of Tuples uses {key, value} not [key, value]
    # Format: [{"key1", "val1"}, {"key2", "val2"}]
    tuple_strs = ['{' + json.dumps(k) + ', ' + json.dumps(v) + '}'
                  for k, v in sorted_items]
    data_str = '[' + ', '.join(tuple_strs) + ']'

    return hashlib.md5(data_str.encode('utf-8')).hexdigest()


def calculate_all_params_md5(job: Dict[str, Any], debug: bool = False) -> str:
    """
    Calculate all_params_md5 for a job configuration

    This follows the same logic as compass-ci's Crystal implementation:
    1. Extract and flatten pp parameters
    2. Extract and flatten ss parameters
    3. Include common parameters (tbox_group, os, os_arch, os_version)
    4. Calculate MD5 of combined parameters

    Args:
        job: Job configuration dictionary
        debug: If True, print debug information about parameters

    Returns:
        MD5 hash string that uniquely identifies the job configuration
    """
    all_params = {}

    # Extract and flatten pp parameters
    if 'pp' in job and job['pp']:
        flat_pp = flat_hh(job['pp'])
        all_params.update(flat_pp)
        if debug:
            print(f"\nFlattened pp parameters ({len(flat_pp)} items):")
            for k, v in sorted(flat_pp.items()):
                print(f"  {k} = {v}")

    # Extract and flatten ss parameters
    if 'ss' in job and job['ss']:
        flat_ss = flat_hh(job['ss'])
        all_params.update(flat_ss)
        if debug:
            print(f"\nFlattened ss parameters ({len(flat_ss)} items):")
            for k, v in sorted(flat_ss.items()):
                print(f"  {k} = {v}")

    # Include common parameters
    for param in COMMON_PARAMS:
        if param in job and job[param] is not None:
            all_params[param] = str(job[param])

    if debug:
        print(f"\nCommon parameters:")
        for param in COMMON_PARAMS:
            if param in all_params:
                print(f"  {param} = {all_params[param]}")

        print(f"\nTotal parameters for MD5: {len(all_params)}")
        print(f"First 10 parameters (sorted):")
        for k, v in sorted(all_params.items())[:10]:
            print(f"  {k} = {v}")

    return get_md5(all_params)




# Example usage and testing
if __name__ == "__main__":
    pass
